﻿// Copyright (C) 2012 Kim Jensen, TaskJunction.com
// Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Json;
using System.Net;
using TaskJunctionLib.Model;
using TaskJunctionLib.Mapping.JSON;
using System.IO;
using System.Collections;

namespace TaskJunctionLib.Communication
{
    public class Client
    {
        public Client()
        {
        }

        public static Client Default()
        {
            if (defaultClient == null)
                defaultClient = new Client();
            return defaultClient;
        }
        private static Client defaultClient;

        public string Host { get { return "http://www.taskjunction.com"; } }
        public string Username { get; set; }
        public string Password { get; set; }

        public User LoggedInUser { get { return loggedInUser; } }
        private User loggedInUser;

        public string Get(string resourceName)
        {
            return doRequest(setupRequest("GET", resourceName));
        }

        public string Post(string resourceName, string data)
        {
            return doRequest(setupRequest("POST", resourceName, data));
        }

        public string Put(string resourceName, string data)
        {
            return doRequest(setupRequest("PUT", resourceName, data));
        }

        public void Delete(string resourceName)
        {
            doRequest(setupRequest("DELETE", resourceName));
        }

        private string resourceURI(string resourceName)
        {
            return Host + "/" + resourceName;
        }
        private HttpWebRequest setupRequest(string method, string resourceName)
        {
            var request = HttpWebRequest.Create(resourceURI(resourceName)) as HttpWebRequest;
            request.Proxy = null;
            request.Timeout = 10000;
            request.CookieContainer = cookieContainer;
            request.Method = method;
            request.Accept = "application/json";
            return request;
        }

        private HttpWebRequest setupRequest(string method, string resourceName, string data)
        {
            var request = setupRequest(method, resourceName);
            request.ContentType = "application/json";
            var bytes = new UTF8Encoding().GetBytes(data);
            var stream = request.GetRequestStream();
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
            return request;
        }

        private string doRequest(HttpWebRequest request)
        {
            HttpWebResponse response;
            try
            {
                response = request.GetResponse() as HttpWebResponse;
            }
            catch (WebException e)
            {
                response = e.Response as HttpWebResponse;
            }

            try
            {
                return handleResponse(response);
            }
            catch (ClientHttpException e)
            {
                if (e.StatusCode == HttpStatusCode.Unauthorized)
                {
                    Login();
                    return doRequest(request);
                }
                else
                {
                    throw e;
                }
            }
            finally  {
                response.Close();
            }
        }

        private string handleResponse(HttpWebResponse response)
        {
            var stream = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            try
            {
                var content = stream.ReadToEnd();
                var status = (int)response.StatusCode;
                if (status < 200 || status > 299)
                {
                    handleErrorResponse(response, content);
                }
                return content;
            }
            finally
            {
                stream.Close();
            }
        }

        private void handleErrorResponse(HttpWebResponse response, string responseBody)
        {
            var msg = responseBody.Trim();
            if (String.IsNullOrEmpty(msg))
            {
                msg = response.StatusDescription;
            }
            else if (response.ContentType.ToLower().Contains("application/json"))
            {
                msg = jsonBasedErrorMessage(msg);
            }
            throw new ClientHttpException(msg, response.StatusCode);
        }

        private string jsonBasedErrorMessage(string body)
        {
            try
            {
                var json = JsonValue.Parse(body);
                Func<JsonValue, string> valueToStr = j =>
                {
                    return ((j is JsonArray) ? String.Join(", ", (j as JsonArray).AsEnumerable<JsonValue>()) : j.ToString()).Trim('"');
                };
                Func<string, string> keyToStr = s =>
                {
                    return (s.Length < 2) ? s : Char.ToUpper(s[0]) + s.Substring(1).Replace('_', ' ');
                };
                var retval = String.Join("\n", json.Select(x => keyToStr(x.Key) + " " + valueToStr(x.Value)));
                if (string.IsNullOrEmpty(retval) && body.Length > 1 && body.StartsWith("\"") && body.EndsWith("\""))
                    return body.Substring(1, body.Length - 2);
                return retval;
            }
            catch (Exception)
            {
                if (body.Length > 1 && body.StartsWith("\"") && body.EndsWith("\""))
                    return body.Substring(1, body.Length - 2);
                return body;
            }
        }

        public void Login()
        {
            loggedInUser = null;
            var j = new JsonObject();
            j.Add("email", Username);
            j.Add("password", Password);
            var request = setupRequest("POST", "sessions", j.ToString(JsonSaveOptions.None));
            try
            {
                var response = request.GetResponse() as HttpWebResponse;
                var data = handleResponse(response);
                loggedInUser = new UserMapper().deserialize(data);
            }
            catch (WebException e)
            {
                if (e.Response == null || !(e.Response is HttpWebResponse))
                    throw e;
                handleResponse(e.Response as HttpWebResponse);
            }
        }

        public void Logout()
        {
            if (loggedInUser != null && loggedInUser.Id != null)
                Delete("sessions/" + loggedInUser.Id);
            loggedInUser = null;
        }

        private CookieContainer cookieContainer = new CookieContainer();
        private Dictionary<Type, IList> cache = new Dictionary<Type, IList>();

        public IList<T> GetObjectCache<T>() where T : ModelEntity
        {

            lock (thisLock)
            {
                return (cache.ContainsKey(typeof(T))) ? cache[typeof(T)] as IList<T> : null;
            }
        }

        public void SetObjectCache<T>(IList<T> list) where T : ModelEntity
        {
            lock (thisLock)
            {
                cache[typeof(T)] = list as IList;
            }
        }

        protected Object thisLock = new Object();
    }
}
